// Fill out your copyright notice in the Description page of Project Settings.


#include "NetworkGameSession.h"
#include "NetworkController.h"
#include "NetWorkSessionSettings.h"
#include "OnlineSubsystemUtils.h"
#include "Online/OnlineSessionNames.h"

ANetworkGameSession::ANetworkGameSession(const FObjectInitializer& ObjectInitializer): Super(ObjectInitializer)
{
	if (!HasAnyFlags(RF_ClassDefaultObject))
	{
		OnCreateSessionCompleteDelegate = FOnCreateSessionCompleteDelegate::CreateUObject(this, &ANetworkGameSession::OnCreateSessionComplete);
		OnStartSessionCompleteDelegate = FOnStartSessionCompleteDelegate::CreateUObject(this, &ANetworkGameSession::OnStartSessionsComplete);
		OnUpdateSessionCompleteDelegate = FOnUpdateSessionCompleteDelegate::CreateUObject(this, &ANetworkGameSession::OnUpdateSessionsComplete);
		OnFindSessionsCompleteDelegate = FOnFindSessionsCompleteDelegate::CreateUObject(this, &ANetworkGameSession::OnFindSessionComplete);
		OnJoinSessionCompleteDelegate = FOnJoinSessionCompleteDelegate::CreateUObject(this, &ANetworkGameSession::OnJoinSessionsComplete);
		// OnDestroySessionCompleteDelegate = FOnDestroySessionCompleteDelegate::CreateUObject(this, &AShooterGameSession::OnDestroySessionComplete);
	}
}

void ANetworkGameSession::DebugMessage(FString Message)
{
	if (GEngine)
	{
		GEngine->AddOnScreenDebugMessage(-1, 60.f, FColor::Red, Message);
	}
}

void ANetworkGameSession::OnCreateSessionComplete(FName SessionsName, bool bWasSuccessful)
{
	IOnlineSubsystem* OnlineSub = Online::GetSubsystem(GetWorld());
	// DebugMessage(FString("OnCreateSessionComplete"));
	if (OnlineSub)
	{
		IOnlineSessionPtr SessionPtr = OnlineSub->GetSessionInterface();
		if (SessionPtr)
		{
			SessionPtr->ClearOnCreateSessionCompleteDelegate_Handle(OnCreateSessionCompleteDelegateHandle);
		}
	}
	OnCreatePresenceSessionComplete().Broadcast(SessionsName, bWasSuccessful);
}

void ANetworkGameSession::OnStartSessionsComplete(FName InSessionName, bool bWasSuccessful)
{
	IOnlineSubsystem* OnlineSub = Online::GetSubsystem(GetWorld());
	// DebugMessage(FString("OnStartSessionsComplete"));
	if (OnlineSub)
	{
		IOnlineSessionPtr SessionPtr = OnlineSub->GetSessionInterface();
		if (SessionPtr.IsValid())
		{
			SessionPtr->ClearOnStartSessionCompleteDelegate_Handle(OnStartSessionCompleteDelegateHandle);
		}
	}

	if (bWasSuccessful)
	{
		for (FConstControllerIterator It = GetWorld()->GetControllerIterator(); It; ++It)
		{
			ANetworkController* PC = Cast<ANetworkController>(*It);
			if (PC && !PC->IsLocalController())
			{
				PC->ClientStartOnlineGame();
			}
		}
	}
}

void ANetworkGameSession::OnUpdateSessionsComplete(FName InSessionName, bool bWasSuccessful)
{
	IOnlineSubsystem* OnlineSub = Online::GetSubsystem(GetWorld());
	if (OnlineSub)
	{
		IOnlineSessionPtr SessionPtr = OnlineSub->GetSessionInterface();
		if (SessionPtr.IsValid())
		{
			SessionPtr->ClearOnUpdateSessionCompleteDelegate_Handle(OnUpdateSessionCompleteDelegateHandle);
		}
	}
	OnUpdateSessionsComplete().Broadcast(InSessionName, bWasSuccessful);
}

void ANetworkGameSession::OnFindSessionComplete(bool bWasSuccessful)
{
	IOnlineSubsystem* OnlineSub = Online::GetSubsystem(GetWorld());
	// DebugMessage(FString("OnFindSessionComplete"));
	if (OnlineSub)
	{
		IOnlineSessionPtr SessionPtr = OnlineSub->GetSessionInterface();
		if (SessionPtr)
		{
			SessionPtr->ClearOnFindSessionsCompleteDelegate_Handle(OnStartSessionCompleteDelegateHandle);
		}
	}
	OnFindSessionsComplete().Broadcast(bWasSuccessful);
}

void ANetworkGameSession::OnJoinSessionsComplete(FName InSessionName, EOnJoinSessionCompleteResult::Type Result)
{
	IOnlineSubsystem* OnlineSub = Online::GetSubsystem(GetWorld());
	// DebugMessage(FString("OnJoinSessionsComplete"));
	if (OnlineSub)
	{
		IOnlineSessionPtr SessionPtr = OnlineSub->GetSessionInterface();
		if (SessionPtr)
		{
			SessionPtr->ClearOnJoinSessionCompleteDelegate_Handle(OnJoinSessionCompleteDelegateHandle);
		}
	}
	OnJoinSessionComplete().Broadcast(Result);
}

bool ANetworkGameSession::HostSession(TSharedPtr<const FUniqueNetId> UserId, FName SessionsName, const FString& GameType, const FString& MapName, bool bIsLAN, bool bIsPresence, int32 MaxNumPlayers)
{
	IOnlineSubsystem* OnlineSub = Online::GetSubsystem(GetWorld());
	if (OnlineSub)
	{
		CurrentSessionParams.UserId = UserId;
		CurrentSessionParams.bIsLAN = bIsLAN;
		CurrentSessionParams.bIsPresence = bIsPresence;
		CurrentSessionParams.SessionName = SessionsName;
		MaxPlayers = MaxNumPlayers;

		IOnlineSessionPtr SessionPtr = OnlineSub->GetSessionInterface();
		if (SessionPtr.IsValid() && CurrentSessionParams.UserId.IsValid())
		{
			HostSetting = MakeShareable(new FNetworkSessionSettings(bIsLAN, bIsPresence, MaxPlayers));
			HostSetting->Set(SETTING_MAPNAME, MapName, EOnlineDataAdvertisementType::ViaOnlineService);
			// HostSetting->Set(SEARCH_KEYWORDS, FString("Custom"), EOnlineDataAdvertisementType::ViaOnlineService);
			OnCreateSessionCompleteDelegateHandle = SessionPtr->AddOnCreateSessionCompleteDelegate_Handle(OnCreateSessionCompleteDelegate);
			// SessionPtr->AddOnSessionFailureDelegate_Handle()
			// DebugMessage(FString("ANetworkGameSession::HostSession"));
			return SessionPtr->CreateSession(*UserId, SessionsName, *HostSetting);
		} else
		{
			OnCreateSessionComplete(SessionsName, false);
		}
	}
#if !UE_BUILD_SHIPPING
	else
	{
		OnCreatePresenceSessionComplete().Broadcast(NAME_GameSession, true);
		return true;
	}
#endif
	return false;
}

bool ANetworkGameSession::HostSession(const TSharedPtr<const FUniqueNetId> UserId, const FName SessionsName, const FOnlineSessionSettings& SessionSettings)
{
	IOnlineSubsystem* OnlineSub = Online::GetSubsystem(GetWorld());
	if (OnlineSub)
	{
		CurrentSessionParams.UserId = UserId;
		CurrentSessionParams.bIsLAN = SessionSettings.bIsLANMatch;
		CurrentSessionParams.bIsPresence = SessionSettings.bUsesPresence;
		CurrentSessionParams.SessionName = SessionsName;
		MaxPlayers = SessionSettings.NumPrivateConnections + SessionSettings.NumPublicConnections;

		IOnlineSessionPtr SessionPtr = OnlineSub->GetSessionInterface();
		if (SessionPtr.IsValid() && CurrentSessionParams.UserId.IsValid())
		{
			OnCreateSessionCompleteDelegateHandle = SessionPtr->AddOnCreateSessionCompleteDelegate_Handle(OnCreateSessionCompleteDelegate);
			return SessionPtr->CreateSession(*UserId, SessionsName, SessionSettings);
		} else
		{
			OnCreateSessionComplete(SessionsName, false);
		}
	}
	return false;
}

void ANetworkGameSession::HandleMatchHasStarted()
{
	IOnlineSubsystem* OnlineSub = Online::GetSubsystem(GetWorld());
	// DebugMessage(FString("HandleMatchHasStarted"));
	if (OnlineSub)
	{
		IOnlineSessionPtr SessionPtr = OnlineSub->GetSessionInterface();
		if (SessionPtr.IsValid() && SessionPtr->GetNamedSession(NAME_GameSession) != nullptr)
		{
			OnStartSessionCompleteDelegateHandle = SessionPtr->AddOnStartSessionCompleteDelegate_Handle(OnStartSessionCompleteDelegate);
			SessionPtr->StartSession(NAME_GameSession);
		}
	}
}

void ANetworkGameSession::FindSession(TSharedPtr<const FUniqueNetId> UserId, FName InSessionName, bool bIsLAN, bool bIsPresence)
{
	IOnlineSubsystem* OnlineSub = Online::GetSubsystem(GetWorld());
	if (OnlineSub)
	{
		IOnlineSessionPtr SessionPtr = OnlineSub->GetSessionInterface();
		CurrentSessionParams.UserId = UserId;
		CurrentSessionParams.bIsLAN = bIsLAN;
		CurrentSessionParams.bIsPresence = bIsPresence;
		CurrentSessionParams.SessionName = InSessionName;
		if (SessionPtr.IsValid() && CurrentSessionParams.UserId.IsValid())
		{
			SearchSettings = MakeShareable(new FNetworkSearchSettings(bIsLAN, bIsPresence));
			SearchSettings->QuerySettings.Set(SEARCH_KEYWORDS, FString("Custom"), EOnlineComparisonOp::Equals);
			OnFindSessionCompleteDelegateHandle = SessionPtr->AddOnFindSessionsCompleteDelegate_Handle(OnFindSessionsCompleteDelegate);
			SessionPtr->FindSessions(*UserId, SearchSettings.ToSharedRef());
		} else
		{
			OnFindSessionComplete(false);
		}
	}
}

void ANetworkGameSession::UpdateSession(FName InSessionName, TSharedPtr<FNetworkSessionSettings> InHostSession)
{
	IOnlineSubsystem* OnlineSub = Online::GetSubsystem(GetWorld());
	if (OnlineSub)
	{
		IOnlineSessionPtr SessionPtr = OnlineSub->GetSessionInterface();
		if (SessionPtr.IsValid())
		{
			HostSetting = InHostSession;
			OnUpdateSessionCompleteDelegateHandle = SessionPtr->AddOnUpdateSessionCompleteDelegate_Handle(OnUpdateSessionCompleteDelegate);
			SessionPtr->UpdateSession(InSessionName, *HostSetting);
			return;
		}
	}
	OnUpdateSessionsComplete().Broadcast(InSessionName, false);
}

bool ANetworkGameSession::JoinSession(TSharedPtr<const FUniqueNetId> UserId, FName InSessionName, int32 SessionIndexInSearchResults)
{
	if (SessionIndexInSearchResults >= 0 && SearchSettings->SearchResults.Num() > SessionIndexInSearchResults)
	{
		return JoinSession(UserId, SessionName, SearchSettings->SearchResults[SessionIndexInSearchResults]);
	}
	return false;
}

bool ANetworkGameSession::JoinSession(TSharedPtr<const FUniqueNetId> UserId, FName InSessionName, const FOnlineSessionSearchResult& SearchResult)
{
	IOnlineSubsystem* OnlineSub = Online::GetSubsystem(GetWorld());
	if (OnlineSub)
	{
		IOnlineSessionPtr SessionPtr = OnlineSub->GetSessionInterface();
		if (SessionPtr.IsValid() && UserId.IsValid())
		{
			OnJoinSessionCompleteDelegateHandle = SessionPtr->AddOnJoinSessionCompleteDelegate_Handle(OnJoinSessionCompleteDelegate);
			return SessionPtr->JoinSession(*UserId, InSessionName, SearchResult);
		}
	}
	return false;
}

void ANetworkGameSession::HandleMatchHasEnded()
{
	IOnlineSubsystem* OnlineSub = Online::GetSubsystem(GetWorld());
	// DebugMessage(FString("HandleMatchHasEnded"));
	if (OnlineSub)
	{
		IOnlineSessionPtr SessionPtr = OnlineSub->GetSessionInterface();
		if (SessionPtr.IsValid() && (SessionPtr->GetNamedSession(NAME_GameSession) != nullptr))
		{
			for (FConstControllerIterator It = GetWorld()->GetControllerIterator(); It; ++It)
			{
				ANetworkController* PC = Cast<ANetworkController>(*It);
				if (PC && !PC->IsLocalController())
				{
					PC->ClientEndOnlineGame();
				}
			}
			SessionPtr->EndSession(NAME_GameSession);
		}
	}
}

const TArray<FOnlineSessionSearchResult>& ANetworkGameSession::GetSearchSessionResult() const
{
	return SearchSettings->SearchResults;
}

const EOnlineAsyncTaskState::Type ANetworkGameSession::GetSearchSessionStatus() const
{
	return SearchSettings.IsValid() ? SearchSettings->SearchState : EOnlineAsyncTaskState::NotStarted;
}
